﻿using System;
using Stauffware.Common.Core;
using Stauffware.Common.Xml.Xerialization;

namespace XmlUnitTests
{
	//#################################################################################
	/// <summary>
	/// This class implements a mapping between the Rational class and its XML representation.
	/// It serves a test data for Xerialization.
	/// </summary>

	public class RationalXMapping : XerialScalarMapping
	{
		public RationalXMapping (XerialContext context) : base(context, typeof(Rational)) { }

		protected override ParseResult FromXmlString (string valueString)
		{
			try
			{
				Rational value = new Rational(valueString);
				return new ParseResult(value, null);
			}
			catch (FormatException x)
			{
				return new ParseResult(null, x.Message);
			}
		}
	}

	//#################################################################################
	/// <summary>
	/// A rational number: the fraction of two integers.
	/// It is an immutable type; once created, values are constant.
	/// This class has been stripped down for use as test data.
	/// </summary>

	public struct Rational : IComparable
	{
		// Much of the implementation has been omitted (e.g. operators & conversions).

		private int _numerator;
		private int _denominator;	// must always be > 0

		public int Numerator { get { return this._numerator; } }
		public int Denominator { get { return this._denominator; } }

		public Rational (int n)
		{
			this._numerator = n;
			this._denominator = 1;
		}

		public Rational (int numerator, int denominator)
		{
			this._numerator = (denominator < 0) ? -numerator : numerator;
			this._denominator = (denominator < 0) ? -denominator : denominator;
			this.Reduce();
		}

		//-----------------------------------------------------------------------------

		public Rational (String str)
		{
			if (str == "") throw new FormatException("invalid number format: blank string");
			bool negative = (str[0] == '-');	// look for leading minus sign
			if (negative) str = str.Substring(1);
			if (str == "") throw new FormatException("invalid number format: no digits after minus sign");

			// look for whole-number portion
			int wholeNum = 0;
			int fracSep = str.IndexOfAny("- ".ToCharArray());
			if (fracSep == 0) throw new FormatException("invalid number format");
			if (fracSep > 0)
			{
				wholeNum = Int32.Parse(str.Substring(0, fracSep));
				str = str.Substring(fracSep+1);
			}

			if (str == "")
			{
				this._numerator = wholeNum;
				this._denominator = 1;
			}
			else
			{	// look for fractional portion				
				int slash = str.IndexOf('/');
				if (slash == 0) throw new FormatException("invalid number format: missing numerator");
				if (slash < 0)
				{
					if (fracSep > 0) throw new FormatException("invalid number format");
					this._numerator = Int32.Parse(str);
					this._denominator = 1;
				}
				else
				{
					this._numerator = Int32.Parse(str.Substring(0, slash));
					this._denominator = Int32.Parse(str.Substring(slash+1));
					if (this._denominator <= 0) throw new FormatException("invalid number format: denominator must be > 0");
					this._numerator += wholeNum * this._denominator;
				}
			}

			if (negative) this._numerator = -this._numerator;
		}

		//-----------------------------------------------------------------------------

		public override bool Equals (Object that)
		{
			if (that == null || this.GetType() != that.GetType())
				return false;
			return (this._numerator == ((Rational) that)._numerator) 
				&& (this._denominator == ((Rational) that)._denominator);
		}

		//-----------------------------------------------------------------------------

		public override int GetHashCode ( ) { return (this._numerator << 8) | (this._denominator & 0xFF); }

		public override string ToString ( ) { return this._numerator.ToString() + "/" + this._denominator.ToString(); }

		public int CompareTo (object that) { return Compare(this, (Rational) that); }

		public static Rational operator - (Rational a, Rational b)
		{	return new Rational((a.Numerator * b.Denominator) - (b.Numerator * a.Denominator), a.Denominator * b.Denominator);	}

		//-----------------------------------------------------------------------------

		public static int Compare (Rational a, Rational b)
		{
			Rational c = a - b;
			if (c._numerator < 0)
				return -1;
			if (c._numerator > 0)
				return 1;
			return 0;
		}

		//-----------------------------------------------------------------------------

		private void Reduce ( )
		{
			if (this._numerator == this._denominator)
				this._numerator = this._denominator = 1;
			else
			{
				int factor = gcd(this._numerator, this._denominator);
				this._numerator /= factor;
				this._denominator /= factor;
			}
		}

		//-----------------------------------------------------------------------------
		/// Computes greatest common denominator using Euclid's algorithm.

		private static int gcd (int m, int n)
		{
			if (m < 0) m = -m;
			if (n < 0) n = -n;

			if (n > m)
			{
				int foo = m;
				m = n;
				n = foo;
			}

			while (n != 0)
			{
				m = m % n;
				if (m == 0) break;
				n = n % m;
			}

			return (m == 0) ? n : m;
		}

	}

	//#################################################################################

}
